#ifndef ANIMATION_H
#define ANIMATION_H

#include <vector>
#include <map>
#include <unordered_map>
#include <glm/glm.hpp>
#include <assimp/scene.h>
#include "Scene/bone.h"
#include <functional>
#include "Scene/animdata.h"
#include "Scene/assimp_glm_helpers.h"

namespace CC
{

	struct AssimpNodeData
	{
		glm::mat4 transformation;
		std::string name;
		int childrenCount;
		std::vector<AssimpNodeData> children;
	};

	class Animation;
	struct AnimationInfo
    {
		std::string Directory;
		int BoneCounter = 0;

        std::unordered_map<std::string, BoneInfo> BoneInfoMap;        
        std::unordered_map<std::string, Animation*> AnimationInfoMap;

        AnimationInfo() = default;
        AnimationInfo(const AnimationInfo &) = default;

		 void Clear(){
		 	Directory = "";
		 	BoneCounter = 0;
		 	BoneInfoMap.clear();

            std::unordered_map<std::string, Animation*>::iterator iter = AnimationInfoMap.begin();
            for (; iter != AnimationInfoMap.end(); ++iter)
                delete iter->second;

		 	AnimationInfoMap.clear();
		 }
    };

	class Animation
	{
	public:
		Animation() = default;

		Animation(const std::string &animationPath, AnimationInfo &animationInfo)
		{
			//        LOGE("Animation %lu", m_Bones.size());
			m_animationPath = animationPath;
			m_Bones.clear();
			Assimp::Importer importer;
			const aiScene *scene;
			try
			{
				scene = importer.ReadFile(animationPath, aiProcessPreset_TargetRealtime_Fast);
			}
			catch (...)
			{
                CC_CORE_INFO("Something wrong with animation.\n");
			}
			if (scene == nullptr)
			{
                CC_CORE_INFO("importer.GetErrorString() %s\n", importer.GetErrorString());
			}

			assert(scene && scene->mRootNode);
            CC_CORE_INFO("HasAnimations=%d", scene->HasAnimations());
            CC_CORE_INFO("mNumAnimations=%d\n", scene->mNumAnimations);
        	if(scene->mNumAnimations <= 0) return;

			auto animation = scene->mAnimations[0];
			m_Duration = (float)animation->mDuration;
            CC_CORE_INFO("Animation dura=%f\n", m_Duration);

            m_Name = animationPath;

			m_TicksPerSecond = (int)animation->mTicksPerSecond;
			aiMatrix4x4 globalTransformation = scene->mRootNode->mTransformation;
			globalTransformation = globalTransformation.Inverse();

			m_globalTransformation = AssimpGLMHelpers::ConvertMatrixToGLMFormat(globalTransformation);
			ReadHeirarchyData(m_RootNode, scene->mRootNode);
			ReadMissingBones(animation, animationInfo);
		}

		~Animation()
		{
		}

		Bone *FindBone(const std::string &name)
		{
			auto iter = std::find_if(m_Bones.begin(), m_Bones.end(),
									 [&](const Bone &Bone)
									 {
										 //				LOGE("name.c_str() %s", name.c_str());
										 //				LOGE("sizeof(m_BoneInfoMap) %d", Bone.GetBoneName() == name);
										 // printf("%s:%s\n", Bone.GetBoneName().c_str(), name.c_str());
										 return Bone.GetBoneName() == name;
									 });
			if (iter == m_Bones.end())
				return nullptr;
			else
				return &(*iter);
		}

		inline float GetTicksPerSecond() { return (float)m_TicksPerSecond; }
		inline float GetDuration() { return m_Duration; }
		inline const AssimpNodeData &GetRootNode() { return m_RootNode; }
        inline std::string GetName() { return m_Name; }
		std::unordered_map<std::string, BoneInfo> &GetBoneIDMap()
		{
			return m_BoneInfoMap;
		}

		glm::mat4 m_globalTransformation;

		std::string m_animationPath;        

	private:
		float m_Duration = 0.0;
		int m_TicksPerSecond = 0;
		std::vector<Bone> m_Bones;
		AssimpNodeData m_RootNode;
		std::unordered_map<std::string, BoneInfo> m_BoneInfoMap;
        std::string m_Name = "default";

		void ReadMissingBones(const aiAnimation *animation, AnimationInfo &animationInfo)
		{
			//        LOGE("m_Bones.size() %lu", m_Bones.size());
			// m_Bones.clear();
			int size = animation->mNumChannels;
			// printf("ReadMissingBones animation->mNumChannels=%d\n", animation->mNumChannels);

			auto &boneInfoMap = animationInfo.BoneInfoMap; // getting m_BoneInfoMap from Model class
			int &boneCount = animationInfo.BoneCounter;	   // getting the m_BoneCounter from Model class

			// reading channels(bones engaged in an animation and their keyframes)
			for (int i = 0; i < size; i++)
			{
				auto channel = animation->mChannels[i];
				std::string boneName = channel->mNodeName.data;
				int boneId = -1;

				if (boneInfoMap.find(boneName) == boneInfoMap.end())
				{
					// boneInfoMap[boneName].id = boneCount;
					// boneCount++;
					// printf("boneInfoMap.find %s\n", boneName.c_str());
				}
				else{
					boneId = boneInfoMap[boneName].id;
				}
				m_Bones.push_back(Bone(boneName, boneId, channel));
				// printf("m_Bones i=%d,%s;id=%d\n", i, channel->mNodeName.data, boneInfoMap[channel->mNodeName.data].id);
			}
			//        LOGE("ReadMissingBones::::m_Bones.size() %lu", m_Bones.size());

			m_BoneInfoMap = boneInfoMap;
		}

		void ReadHeirarchyData(AssimpNodeData &dest, const aiNode *src, int space = 0)
		{
			//        LOGE("ReadHeirarchyData %lu", m_Bones.size());
			 
			assert(src);

			dest.name = src->mName.data;
			dest.transformation = AssimpGLMHelpers::ConvertMatrixToGLMFormat(src->mTransformation);
			dest.childrenCount = src->mNumChildren;

			// printf("ReadHeirarchyData =%s\n", dest.name.c_str());
           // for (int s = 0; s < space; s++) { CC_CORE_INFO("-"); }
            //    CC_CORE_INFO("ReadHeirarchyData, %s>%d\n", src->mName.data, src->mNumChildren);

			for (int i = 0; i < (int)src->mNumChildren; i++)
			{
				AssimpNodeData newData;
				ReadHeirarchyData(newData, src->mChildren[i], space + 1);
				dest.children.push_back(newData);
			}
		}
	};

}
#endif